package net.hz.autohp;



import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import net.hz.utils.Constants.*;
import net.hz.utils.Constants.GatewayServers;
import net.hz.utils.Constants.MissionType;
import net.hz.utils.HttpLoad;

import org.jsoup.connect.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class Gateway {
	private Map<String, String> gCookies = null;

	public Gateway(Map<String, String> Cookies) {
		gCookies = Cookies;
	}
	
	
	//TODO: Create Process to Crack a List of Servers
	//		- Get list of missions, filtered by faction or type of mission
	//		- start highest version of FWB
	//		- based on count of IPs start as many instances of PWB as possible
	//		- Iterate through distinct IPs in misison list
	//		- Connect to the IP
	//		- Select unused PWB process and verify Version is high enough 
	//		- start crack
	//		- add ip to db for future use
	
	//TODO: Create process to Decrypt os.sock and Activate
	// 		- Get list of server destroy missions
	//		- start highest version of UHF and DEC
	//		- Crack server (see above)
	//		- setup timer poll to check when server is cracked
	//		- on server crack, connect to server and start decrypt process
	//		- use poll timer to check for completed decrypt processes
	//		- on decrypt complete, connect to server and activate os.sock
	
	//Find highest version of File
	//- Load file system list, decode the data using jscript enging
	//- iterate through the specific file type being search for
	//- compare against current version and if higher set current
	//	version and save file pid
	//- repeat process until all files of type have been searched
	private Element findHighestFileVersion(Files fileType) {
		Element highestVerFile = null;
		double version = 0;
		
		ScriptEngineManager factory = new ScriptEngineManager();
		ScriptEngine engine = factory.getEngineByName("JavaScript");
		Document gateway = loadGatewayFiles();
		
		Document filesDoc = Document.createShell("http://hacker-project.com");
		Element table = filesDoc.body().appendElement("table").appendElement("tbody");

		for (Element fileScript : gateway.select("script[language=JavaScript]")) {
			if (fileScript.html().startsWith("document.write")) {
				//it's what we are looking for
				try {
					//execute the script text, minus the document write 
					//and append the resulting unencoded html to the Document
					table.appendElement("tr").append((String) engine.eval(fileScript.html().replace("document.write(", "").replace("))", ")")));
				} catch (ScriptException e) {
					e.printStackTrace();
				}
			}
		}

		//we now should have ourselves a document of the files to return
		for (Element file : filesDoc.select("tr:has(td.p1:contains(" + fileType.text() + "))")) {
			double curVersion = Double.parseDouble(file.child(5).text());
			if (curVersion > version)
				highestVerFile = file;
		}
		
		return highestVerFile;
	}
	
	private double getHighestFileVersion(Files fileType) {
		double version = 0;
		Element file = findHighestFileVersion(fileType);
		if (file != null) 
			version = Double.parseDouble(file.child(5).text());
		
		return version;
	}
	
	private String getProccessFileID(Files fileType) {
		Element fileElement = findHighestFileVersion(fileType);
		
		return fileElement.select("td.sni").get(0).text();
	}
	
	private void startProcess(Files fileType) {
		boolean isHigherVersion = false;
		
		Element fileElement = findHighestFileVersion(fileType);
		if ( fileElement != null) {
			//get it's version
			double version = Double.parseDouble(fileElement.child(5).text());
			//now search running processes for it
			Document tasks = getGatewayTasks(TaskSortFilter.ENDING_TIME, GatewayServers.ONE);
			if (tasks.select("tr:has(td.p1:contains(" + fileType.text() + ")").size() != 0) {
				//we have at least one running process, check if they are higher ver
				// otherwise kill them
				for (Element task : tasks.select("tr:has(td.p1:contains(" + fileType.text() + ")")) {
					//get the task id for rem server, this is local task Id + 1
					if (Double.parseDouble(task.select("td.sm2").get(0).text()) <= version) {
						//it's lower terminate it
						killProcess(task.select("td.snl").get(0).text());
					} else {
						isHigherVersion = true;
					}
				}
			}
			
			if (!isHigherVersion)
				startProcess(fileElement.select("td.sni").get(0).text());
		}
	}
	
	public void searchAndDestroy(GatewayServers server) {
		try {
			//loadDecryptMissions(server);

			Element missions = getMissionPage(0, MissionType.DESTROY);

			//find missions that have admin access
			for (Element mission : missions.select("tr.dbg:has(span.green):contains(Yes)")) {
				//we need to start decrypting these
			}
			
			//find missions without admin access
			// we need to start cracking these ones
			for (Element mission : missions.select("tr.dbg:has(span.red):contains(No)")) {
				//load up the server
				executeURL(mission.select("a:contains(Connect)").get(0).absUrl("href"));
				
				Document crackServer = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "connect", "a3", "crack");
				int fwbOver = 0;
				while (crackServer.select("span.green:contains(Target firewall has been bypassed...)").size() <= 0){
					//we need to overload FWB
					fwbOver++;
					crackServer = postGatewayDoc("action", "gate", "a2", "connect", "a3", "crack", "a4", "over_fw", "sel_over", Integer.toString(fwbOver));
				}
				
				//find a unused PWB to use
				String pwdID = "";
				for (Element option : crackServer.select("form[name=frm_crack]").get(0).select("select.sel2 > option")) {
					if (option.text().contains(",") == false) {
						pwdID = option.attr("value");
						break;
					}
				}
				
				URL url = new URL("http://hacker-project.com/index.php");//getting the url for tha page to be parsed.
				URLConnection conn = url.openConnection();
				
				conn.setRequestProperty("Cookie", "PHPSESSID=" + gCookies.get("PHPSESSID"));
				
				conn.setDoOutput(true);
				OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
				writer.write("Button1=select&action=gate&a2=connect&a3=crack&start=1&sel_break=" + pwdID);//parameter  input for POST
				writer.flush();
				
				crackServer = Jsoup.parse(conn.getInputStream(), "UTF-8", "http://hacker-project.com/index.php");
				
				crackServer = postGatewayDoc("action", "gate", "a2", "connect", "a3", "crack", "start", "1", "sel_break", pwdID);
				int pwbOver = 0;
				while (crackServer.select("span.blue:contains(Crack program successfully started.)").size() <= 0) {
					//pwbOver++;
					crackServer = postGatewayDoc("action", "gate", "a2", "connect", "a3", "crack", "a4", "over_cr", "sel_over", Integer.toString(pwbOver));
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/***
	 * Loads up processes needed to perform search and destroy missions
	 * Loops through the IP database filtered by Faction and accepts
	 * Search and Destroy missions that can be performed using current
	 * unhide and decrypt versions. For each mission accepted a password
	 * break is loaded up. Leaves room for overloading.
	 * 
	 * @param server {@link GatewayServers} server to perform missions
	 */
	private void loadDecryptMissions(GatewayServers server) {
		int incrementBy = 0;
		
		loadGatewayDoc(server);
		
		startProcess(Files.FIREWALL_BYPASS);
		
		//figure out the highest version of PWD break is for later use
		double pwdBreak = getHighestFileVersion(Files.PASSWORD_BREAK);
		String pwdBrkFID = getProccessFileID(Files.PASSWORD_BREAK);
		
		//figure out what our highest Unhide and Decryptor versions are
		double decryptVer = getHighestFileVersion(Files.DECRYPTOR);
		double unhideVer = getHighestFileVersion(Files.UNHIDE_FILES);
		
		
		if ((decryptVer > 0) && (unhideVer > 0)) {
			startProcess(Files.DECRYPTOR);
			startProcess(Files.UNHIDE_FILES);
			
			//now we'll find some missions to do
			try {
				Elements IPDb = loadIPDatabase(IPDatabaseFilter.FACTION, incrementBy);
				
				while (IPDb.select("tr.dbg:has(td.green)").size() > 0) {
  					for (Element factionServer : IPDb.select("tr.dbg:has(td.green)")) {
						executeURL(factionServer.child(0).select("a:contains(Connect)").get(0).absUrl("href"));
						
						Document bbs = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "secret_board");
						
						for (Element mission : bbs.body().select("table:has(tr.dbg) tr.dbg:has(td.p1:contains(" + MissionType.DESTROY.name() + "))")) {
							//exit out if we can't run anymore PWD break processes
							if (getFreeProcessPower(server) < ((Files.PASSWORD_BREAK.cpuUsage(pwdBreak)) * 10)) {
								return;
							}
							
							String mid = mission.select("a").get(0).attr("href");
							mid = mid.substring(mid.indexOf("&mid=") + 5);
							
							//review the mission and check that we have the software to perform it
							Document details = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "secret_board", "a3", "review", "mid", mid);
							
							//<span class="red">encrypted with V 1.88</span>
							//<span class="red">hidden with V 1.95</span>
							String encrypted = details.select("table.emi5").select("span.red").get(0).text();
							String hidden = details.select("table.emi5").select("span.red").get(1).text();
							
							double encryptVer = Double.parseDouble(encrypted.substring(encrypted.indexOf("V") + 2));
							double hiddenVer = Double.parseDouble(hidden.substring(hidden.indexOf("V") + 2));
							
							if ((decryptVer >= encryptVer) && (unhideVer >= hiddenVer)) {
								//we can do this mission
								System.out.print("accepting mission; encrypted V " + encryptVer + " & hidden V " + hiddenVer + "\n"); 
								executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "secret_board", "a3", "accept", "mid", mid);
								startProcess(pwdBrkFID);
							}						
						}
					}
					
					incrementBy = incrementBy + 20;
					IPDb = loadIPDatabase(IPDatabaseFilter.FACTION, incrementBy);
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			System.out.print("Couldn't find Decrypt and/or Unhide software");
		}
		
		
	}
	
	
	//TODO: Refactor this if possible
	public void performDeleteMissions() {
		String newServerIP = "";
		String currentServerIP = "";
		int missionCount = 0;
		
		try {
			//load gateway one to perform delete missions
			loadGatewayDoc(GatewayServers.ONE);
			
			//load up the gateway and check how much process power it has free
			int missionsCanDo = (int) getFreeProcessPower(GatewayServers.ONE) / 100;
			System.out.print("Can do " + missionsCanDo + " missions\n");
			
			Element body = getMissionPage(0, MissionType.DELETE);	//get first page always
			System.out.print("Mission Data Loaded\n");
			do {
				//loop through the missions
				for (Element missionData : body.select("tr.dbg:has(span.green):contains(Yes)")) {
					newServerIP = missionData.child(1).select("tbody").get(0).child(0).select("span.green").text();
					
					if (newServerIP.compareTo(currentServerIP) != 0) {
						//connect to the server
						System.out.print("Connecting to " + newServerIP + '\n');
						executeURL(missionData.child(1).select("tbody").get(0).child(1).select("td").get(0).child(0).absUrl("href"));
					} 
					
					String fileText = missionData.child(3).select("span.sm2 > span.p1").text();
					fileText = fileText.substring(0, fileText.indexOf(", "));
					
					System.out.print("Deleting file #" + fileText + "\n");
					
					executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "files", "a3", "try_delete", "rem", "1", "pid", fileText);

					//mark the mission
					executeURL(missionData.child(0).select("tbody").get(0).child(1).select("a").get(0).absUrl("href"));
					
					currentServerIP = newServerIP;
					missionsCanDo--;
					
					//we can't do any more missions, break for loop
					if (missionsCanDo <= 0) {
						break;
					}
				}
				
				body = getMissionPage(missionCount,  MissionType.DELETE);
				while (body.select("tr.dbg:has(span.green):contains(Yes)").size() == 0) {
					//move to the next page
					missionCount = missionCount + 20;
					body = getMissionPage(missionCount,  MissionType.DELETE);
					if (body.select("tr.dbg").size() == 0)
						break;
				}
				
			} while ((body.select("tr.dbg").size() > 0) && (missionsCanDo > 0));
			
			if (body.select("tr.dbg").size() == 0) {
				System.out.print("No more missions");
			} else if ((missionsCanDo <= 0)) {
				System.out.print("No more processing power");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/***
	 * Searches the mission page for servers with no IP data. Connects to 
	 * this server and adds it to the IP database.
	 */
	public void findNewServers() {
		boolean tryNextPage = false;
		int nextPageItemCount = 0;
		String currentServerIP = "";
		String newServerIP = "";
		
		try {
			do {
				Element body = getMissionPage(nextPageItemCount,  MissionType.DELETE);
				tryNextPage = (body.select("tr.dbg").size() > 0) ? true : false;
				for (Element server : body.select("tr.dbg:has(span.red):contains(IP Data N/A)")) {
					newServerIP = server.child(1).select("tbody").get(0).child(0).select("span.green").text();
					
					if (newServerIP.compareTo(currentServerIP) != 0) {
						executeURL(server.child(1).select("tbody").get(0).child(1).select("td").get(0).child(0).absUrl("href"));
						
						currentServerIP = newServerIP;
						System.out.print("IP " + currentServerIP + " added to Database\n");
						executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "connect", "a3", "add_ip");
					}
				}
				
				nextPageItemCount = nextPageItemCount + 20;
			} while (tryNextPage == true);
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
	/***
	 * Loads up a list of IP's from DB filtering by Server Type {@link IPDatabaseFilter}, faction etc.<br>
	 * Deteremines based on flag set in {@link MissionType} if we are searching
	 * the standard BBS or Secret BBS.<br>
	 * Iterates through the servers returned.<br>
	 * Connects to the Server and loads up it's determined BBS board (ie. Secret or Standard).<br>
	 * Accepts all mission of the specified type.<br>
	 * 
	 * @param IPTypeFilter 	- {@link IPDatabaseFilter} 
	 * @param mission      	- {@link MissionType}
	 * 
	 * 
	 * @throws IOException
	 * 
	 * @author siegleda
	 */
	public void getMissions(IPDatabaseFilter IPTypeFilter, MissionType mission) throws IOException {
		int incrementBy = 0;
		Elements IPDb = null;
		
		try {
			System.out.print("Search IP DB for Missions");
			IPDb = loadIPDatabase(IPTypeFilter, incrementBy);
			
			while (IPDb.select("tr.dbg:has(td.green)").size() > 0) {
				for (Element server : IPDb.select("tr.dbg:has(td.green)")) {
					executeURL(server.child(0).select("a:contains(Connect)").get(0).absUrl("href"));
					acceptMissionsFromServerBBS(mission.isFaction(), mission);
				}
				
				incrementBy = incrementBy + 20;
				IPDb = loadIPDatabase(IPTypeFilter, incrementBy);
			}
			
			System.out.print("Finished\n");
		} catch (IOException e) {
			throw e;
		}
	}
	
	
	
	/***
	 * 
	 * @param isFaction	- boolean value to determine BBS to load
	 * @param type		- {@link MissionType} to load
	 * @throws IOException
	 * 
	 * @author docslax 
	 */
	private void acceptMissionsFromServerBBS(boolean isFaction, MissionType type) throws IOException {
		Document bbs;
		try {
			bbs = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", (isFaction) ? "secret_board" : "b_board" );
			
			for (Element mission : bbs.body().select("table:has(tr.dbg) tr.dbg:has(td.p1:contains(" + type.name() + "))")) {
				String mid = mission.select("a").get(0).attr("href");
				mid = mid.substring(mid.indexOf("&mid=") + 5);
				
				executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", (isFaction) ? "secret_board" : "b_board", "a3", "accept", "mid", mid);
			}
		} catch (IOException e) {
			throw e;
		}
	}
	
	/***
	 * 
	 * @param startNumber		- Where to start displaying the mission data in terms of number of missions
	 * @param type				- {@link MissionType} of missions to display
	 * @return {@link Element} containing the document's body.
	 */
	private Element getMissionPage(int startNumber, MissionType type) {
		Document doc = null;
		try {
			doc = executeURL("http://hacker-project.com/index.php", "action", "mission", "stype", "0", "tid", "0", "mtype", Integer.toString(type.id()), "order", "", "_o", Integer.toString(startNumber));
		} catch (IOException e) {
			e.printStackTrace();
		} 
		
		return doc.body();
	}
	
	/***
	 * 
	 * @param URL		- the root URL to execute including any query string items
	 * @return {@link Document}
	 * @throws IOException
	 */
	private Document executeURL(String URL) throws IOException {
		try {
			return HttpLoad.getDocument(URL, gCookies);
		} catch (IOException e) {
			throw e;
		}
	}

	/***
	 * 
	 * @param URL		- the root URL to execute
	 * @param keyvals	- A set of key value pairs used to create a query string like:<br/>
	 * 					 <code>?name=test&language=Java&script=js</code>
	 * @return {@link Document}
	 * @throws IOException
	 */
	private Document executeURL(String URL, String... keyvals) throws IOException {
		try {
			return HttpLoad.getDocument(URL, gCookies, keyvals);
		} catch (IOException e) {
			throw e;
		}
	}
	
	/***
	 * 
	 * @param server  - {@link GatewayServers} ID of the gateway server as a string 
	 * @return {@link Integer}		- processing (CPU) power remaining on gateway
	 */
	private int getFreeProcessPower(GatewayServers server) {
		int free = 0;
		
		Elements status = loadGatewayDoc(server).select("form[name=frm_files] span.g");
		
		long using = Long.parseLong(status.get(0).text().replace(",", ""));
		long total = Long.parseLong(status.get(1).text().replace(",", ""));

		free = (int) (total - using);

		return free;
	}
	
	/***
	 * Completes tasks on the gateway server specified
	 * @param server	- {@link GatewayServers}
	 */
	public void completeTasks(GatewayServers server) {
		Long taskId = null;
		Elements completedDeleteTasks = null;
		
		try {
			completedDeleteTasks = getGatewayTasks(TaskSortFilter.ENDING_TIME, server)
					.select("tr:has(a:contains(task complete)):not(tr:has(span:contains(localhost)))");
			do {
				System.out.print("Found " + completedDeleteTasks.size() 
						+ " task to Complete\n");
				
				for (Element task : completedDeleteTasks) {
					//get the task id for rem server, this is local task Id + 1
					taskId = Long.parseLong(task.select("td.snl").get(0).text()) + 1;
					
					System.out.print("Complete Remote Task ID " + taskId + "\n");
					String taskURL = task.select("a:contains(task complete)")
							.get(0).absUrl("href");
					int taskIdPos = taskURL.indexOf("&task") + taskURL
							.substring(taskURL.indexOf("&task")
							, taskURL.indexOf("=", taskURL.indexOf("&task")))
							.length() + 1;
					
					String newURL =taskURL.substring(0, taskIdPos);;
					newURL += taskId.toString() + "_o=0&rem=1";
					
					executeURL(newURL);
				}
				
				completedDeleteTasks = getGatewayTasks(TaskSortFilter.ENDING_TIME, server)
						.select("tr:has(a:contains(task complete)):not(tr:has(span:contains(localhost)))");
			} while (completedDeleteTasks.size() > 0);
			
			System.out.print("Finished Completing Tasks\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/***
	 * 
	 * @param filter		- {@link IPDatabaseFilter} Enumeration
	 * @param startItem		- integer value that specifies the start of the position of the list.. i.e. 20
	 * 
	 * @return 				{@link Elements} containing a list of IP's found using param criteria
	 * 
	 * @throws IOException
	 * 
	 * @author siegleda
	 */
	private Elements loadIPDatabase(IPDatabaseFilter filter, int startItem) throws IOException {
		Document doc = null;
		
		try {
			doc = executeURL("http://hacker-project.com/index.php", "action", "ip_db", "a2", filter.code(), "_o", Integer.toString(startItem));
			
			return doc.body().select("tr.dbg:has(td.green)");
		} catch (IOException e) {
			throw e;
		}
	}
	
	/***
	 * 
	 * @param filter	- {@link TaskSortFilter} 
	 * @param server
	 * @return
	 */
	private Document getGatewayTasks(TaskSortFilter filter, GatewayServers server) {
		ScriptEngineManager factory = new ScriptEngineManager();
		ScriptEngine engine = factory.getEngineByName("JavaScript");
		Document tasksDoc = Document.createShell("http://hacker-project.com");
		
		loadGatewayDoc(server);
		
		//sort the gateway task by ending time
		Document gateway = postGatewayDoc("action", "gate", "a2", "run", "sel_filter", filter.code());
		Element table = tasksDoc.body().appendElement("table").appendElement("tbody");

		for (Element task : gateway.select("script[language=JavaScript]")) {
			if (task.html().startsWith("document.write")) {
				//it's what we are looking for
				try {
					//execute the script text, minus the document write 
					//and append the resulting unencoded html to the Document
					table.appendElement("tr").append((String) engine.eval(task.html().replace("document.write(", "").replace("))", ")")));
				} catch (ScriptException e) {
					e.printStackTrace();
				}
			}
		}

		//we now should have ourselves a document of the tasks to return
		return tasksDoc;
	}

	private Document loadGatewayFiles() {
		Document gateway = null;
		try {
			gateway = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "files");
		} catch (IOException e) {
			e.printStackTrace();
		}

		return gateway;
	}

	private Document killProcess(String pid) {
		Document gateway = null;
		try {
			gateway = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "run", "k_pid", pid);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return gateway;
	}
	
	private Document startProcess(String pid) {
		Document gateway = null;
		try {
			gateway = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "run", "pid", pid);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return gateway;
	}

	private Document loadGatewayDoc(GatewayServers server) {
		Document gateway = null;
		try {
			gateway = executeURL("http://hacker-project.com/index.php", "action", "gate", "a2", "run", "g_id", server.id());
		} catch (IOException e) {
			e.printStackTrace();
		}

		return gateway;
	}

	private Document postGatewayDoc(String... keyvals) {
		Document doc = null;
		
		try {
			doc = HttpLoad.postPage("http://hacker-project.com/index.php", gCookies, keyvals);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return doc;
	}
}


